<?php

namespace yii\base;

use yii\helpers\StringHelper;

/**
 * Event 是所有事件对象的基类
 *
 * 封装了事件的相关属性。
 * [[sender]] 属性是事件的发起者
 * [[handled]] 标志事件是否被处理.
 * 如果事件的 [[handled]] 被置为了 `true`，那么剩下的处理程序将不会再被调用
 *
 * 此外，在添加事件处理程序时，可以传递额外的数据，并在在事件处理程序中通过 [[data]] 属性使用。
 *
 * For more details and usage information on Event, see the [guide article on events](guide:concept-events).
 *
 */
class Event extends BaseObject
{
    /**
     * @var string 该属性通过 [[Component::trigger()]] 和  [[trigger()]] 设置，
     * 处理程序可以通过该属性判断正在处理的事件
     */
    public $name;
    /**
     * @var object 事件的发起者，如果未设置，将会被设置为调用 `trigger()` 方法的对象。
     * 在静态上下文中触发类级别的事件时，该属性为 "null"
     */
    public $sender;
    /**
     * @var bool 是否处理了该事件。 默认为 "false",
     *  当处理程序将其设置为 "true" 时，事件处理将停止并忽略其余的未调用的事件处理程序。
     */
    public $handled = false;
    /**
     * @var mixed 附加事件处理程序时传递给 [[Component :: on()]]的数据。
     * Note that this varies according to which event handler is currently executing.
     * 请注意，这取决于当前正在执行的事件处理程序。
     */
    public $data;

    /**
     * @var array 包含所有全局注册的事件处理程序。
     */
    private static $_events = [];
    /**
     * @var array 附加了通配符模式的全局注册事件处理程序（事件名称 wildcard => handlers）
     * @since 2.0.14
     */
    private static $_eventWildcards = [];


    /**
     * 为类级事件添加监听
     *
     * 当类级别事件被触发时，该类和父类所有处理程序都会被调用。
     *
     * For example, the following code attaches an event handler to `ActiveRecord`'s
     * `afterInsert` event:
     *
     * ```php
     * Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
     *     Yii::trace(get_class($event->sender) . ' is inserted.');
     * });
     * ```
     *
     * The handler will be invoked for EVERY successful ActiveRecord insertion.
     *
     * 在 2.0.14 之后的版本，
     * 可以用通配符指定类名或事件名
     * you can specify either class name or event name as a wildcard pattern:
     *
     * ```php
     * Event::on('app\models\db\*', '*Insert', function ($event) {
     *     Yii::trace(get_class($event->sender) . ' is inserted.');
     * });
     * ```
     *
     * 有关如何声明事件处理程序的详细信息，请参阅[[Component :: on（）]].
     *
     * @param string $class 要添加事件监听的类的完整类名
     * @param string $name 要监听的事件名
     * @param callable $handler 事件处理函数
     * @param mixed $data 事件触发是，数据将会被传递给处理程序，处理程序可以通过 [[Event::data]] 进行访问
     * @param bool $append 处理程序是否放已有程序在最后
     * @see off()
     */
    public static function on($class, $name, $handler, $data = null, $append = true)
    {
        $class = ltrim($class, '\\');

        if (strpos($class, '*') !== false || strpos($name, '*') !== false) {
            if ($append || empty(self::$_eventWildcards[$name][$class])) {
                self::$_eventWildcards[$name][$class][] = [$handler, $data];
            } else {
                array_unshift(self::$_eventWildcards[$name][$class], [$handler, $data]);
            }
            return;
        }

        if ($append || empty(self::$_events[$name][$class])) {
            self::$_events[$name][$class][] = [$handler, $data];
        } else {
            array_unshift(self::$_events[$name][$class], [$handler, $data]);
        }
    }

    /**
     * 取消类级事件的监听
     *
     * [[on()]] 的逆操作。
     *
     * 注意: 如果为类名或事件名传递了通配符模式，则只删除使用此通配符注册的处理程序，
     * 而使用与此通配符匹配的普通名称注册的处理程序将保留。
     *
     * @param string $class 要添加事件监听的类的完整类名
     * @param string $name 要监听的事件名
     * @param callable $handler 事件处理函数
     * 如果为 `null`，该事件的所有监听都会被移除
     * @return bool 处理程序是否存在，且被成功移除
     * @see on()
     */
    public static function off($class, $name, $handler = null)
    {
        $class = ltrim($class, '\\');
        if (empty(self::$_events[$name][$class]) && empty(self::$_eventWildcards[$name][$class])) {
            return false;
        }
        if ($handler === null) {
            unset(self::$_events[$name][$class]);
            unset(self::$_eventWildcards[$name][$class]);
            return true;
        }

        // plain event names
        if (isset(self::$_events[$name][$class])) {
            $removed = false;
            foreach (self::$_events[$name][$class] as $i => $event) {
                if ($event[0] === $handler) {
                    unset(self::$_events[$name][$class][$i]);
                    $removed = true;
                }
            }
            if ($removed) {
                self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
                return $removed;
            }
        }

        // wildcard event names
        $removed = false;
        foreach (self::$_eventWildcards[$name][$class] as $i => $event) {
            if ($event[0] === $handler) {
                unset(self::$_eventWildcards[$name][$class][$i]);
                $removed = true;
            }
        }
        if ($removed) {
            self::$_eventWildcards[$name][$class] = array_values(self::$_eventWildcards[$name][$class]);
            // remove empty wildcards to save future redundant regex checks :
            if (empty(self::$_eventWildcards[$name][$class])) {
                unset(self::$_eventWildcards[$name][$class]);
                if (empty(self::$_eventWildcards[$name])) {
                    unset(self::$_eventWildcards[$name]);
                }
            }
        }

        return $removed;
    }

    /**
     * 移除所有已注册的类级事件监听
     * @see on()
     * @see off()
     * @since 2.0.10
     */
    public static function offAll()
    {
        self::$_events = [];
        self::$_eventWildcards = [];
    }

    /**
     * 判断指定的类级别时间是否已经添加监听
     * 需要注意的是，该方法也会检测父类是否有对应的处理事件
     *
     * @param string|object $class 对象或完整类名
     * @param string $name 事件名.
     * @return bool 是否已添加监听.
     */
    public static function hasHandlers($class, $name)
    {
        if (empty(self::$_eventWildcards) && empty(self::$_events[$name])) {
            return false;
        }

        if (is_object($class)) {
            $class = get_class($class);
        } else {
            $class = ltrim($class, '\\');
        }

        $classes = array_merge(
            [$class],
            class_parents($class, true),
            class_implements($class, true)
        );

        // regular events
        foreach ($classes as $class) {
            if (!empty(self::$_events[$name][$class])) {
                return true;
            }
        }

        // wildcard events
        foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
            if (!StringHelper::matchWildcard($nameWildcard, $name)) {
                continue;
            }
            foreach ($classHandlers as $classWildcard => $handlers) {
                if (empty($handlers)) {
                    continue;
                }
                foreach ($classes as $class) {
                    if (!StringHelper::matchWildcard($classWildcard, $class)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     * 触发一个类级事件
     *
     * 该方法会触发指定类及其父类对于给出事件的处理函数
     *
     * @param string|object $class 对象或完整类名
     * @param string $name 事件名.
     * @param Event $event 事件对象，如果未设置，将会创建默认的事件对象
     * the event parameter. If not set, a default [[Event]] object will be created.
     */
    public static function trigger($class, $name, $event = null)
    {
        $wildcardEventHandlers = [];
        foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
            if (!StringHelper::matchWildcard($nameWildcard, $name)) {
                continue;
            }
            $wildcardEventHandlers = array_merge($wildcardEventHandlers, $classHandlers);
        }

        if (empty(self::$_events[$name]) && empty($wildcardEventHandlers)) {
            return;
        }

        if ($event === null) {
            $event = new static();
        }
        $event->handled = false;
        $event->name = $name;

        if (is_object($class)) {
            if ($event->sender === null) {
                $event->sender = $class;
            }
            $class = get_class($class);
        } else {
            $class = ltrim($class, '\\');
        }

        $classes = array_merge(
            [$class],
            class_parents($class, true),
            class_implements($class, true)
        );

        foreach ($classes as $class) {
            $eventHandlers = [];
            foreach ($wildcardEventHandlers as $classWildcard => $handlers) {
                if (StringHelper::matchWildcard($classWildcard, $class)) {
                    $eventHandlers = array_merge($eventHandlers, $handlers);
                    unset($wildcardEventHandlers[$classWildcard]);
                }
            }

            if (!empty(self::$_events[$name][$class])) {
                $eventHandlers = array_merge($eventHandlers, self::$_events[$name][$class]);
            }

            foreach ($eventHandlers as $handler) {
                $event->data = $handler[1];
                call_user_func($handler[0], $event);
                if ($event->handled) {
                    return;
                }
            }
        }
    }
}
